'''
Author: LostPatrol_3
Date: 2023-11-04 17:19:48
LastEditors: LostPatrol_3
LastEditTime: 2023-11-08 19:16:34
Description: Implementation of client
'''
import typing
from PyQt5.QtWidgets import QListWidgetItem
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QThread
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QTextImageFormat
from PyQt5.QtGui import QTextCursor
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QImage
from datetime import datetime
import socket
import json
import time
import sys
import os

from GUIFrame import MyFrame
from SocketWorker import *

IP = '8.130.106.82'
PORT = 12345

PATCH_SIZE = 4096  # 4KB
MAX_FILE_LENGTH = 1024 * 1024 * 10 # 10MB
LISTEN_INTERVAL = 200

OTHER_ERR = -1
SOCKET_ERR = 0

TEXT_INVALID = 1
TEXT_SENT    = 2
TEXT_RECV    = 3
FILE_RECEIVED = 4

IMAGE_INVALID = 'image invalid'
IMAGE_SENT    = 'image sent'
IMAGE_RECV    = 'image recv'
IMAGE_DATA    = 'image data'


# message form:
"""
    text = {
        'type' : 'text'
        'time' : str
        'user' : str
        'data' : str
    }

    image_head = {
        'type' : 'image'
        'time' : str
        'user' : str
        'imageSize' : int
    } 

    image_body : bytes

    u
    
    online_members = [ user1, ip1, user2, ip2, ...... ]  # [str]

"""


class ClientGUI(MyFrame):
    sendTextWorkSignal = pyqtSignal(list)  # [userName, sendTime, textContent]
    sendImageWorkSignal = pyqtSignal(list) # [userName, sendTime, imagePath]
    sendFileWorkSignal = pyqtSignal(list)   # [userName, sendTime, filePath, fileType]
    userNameUpdateSignal = pyqtSignal(list) # [clientIP, oldName, newName]
    def __init__(self):
        super().__init__()
        
        self.socketWorker = None
        self.socketThread = None
        self.isListening = False
        self.memberList = []
        self.clientIP = socket.gethostbyname(socket.gethostname())
        self.selfListItem = QListWidgetItem(self.userName + ' ' + self.clientIP)
        # self.QListWidget_memberList.addItem(self.selfListItem)
        self.timer = QTimer()
        self.timer.start(LISTEN_INTERVAL)

        self.createFunctions()
        self.switchState(False)
    
    @pyqtSlot()
    def resetTimer(self):
        self.timer.stop()
        self.timer.start(LISTEN_INTERVAL)
    
    def switchState(self, connected:bool):
        if connected:
            self.QPushButton_applyServerIP.setEnabled(False)
            self.QPushButton_applyServerPort.setEnabled(False)
            self.QPushButton_connect.setEnabled(False)
            self.QPushButton_disconnect.setEnabled(True)
            self.QPushButton_send.setEnabled(True)
        else:
            self.QPushButton_applyServerIP.setEnabled(True)
            self.QPushButton_applyServerPort.setEnabled(True)
            self.QPushButton_connect.setEnabled(True)
            self.QPushButton_disconnect.setEnabled(False)
            self.QPushButton_send.setEnabled(False)
            

    def createFunctions(self):
        self.QLabel_userName.mousePressEvent = self.toggleEditUserName
        self.QLineEdit_userName.returnPressed.connect(self.updateUserName)

        self.QPushButton_applyServerIP.clicked.connect(self.updateServerIP)
        self.QPushButton_applyServerPort.clicked.connect(self.updateServerPort)
        self.QPushButton_send.clicked.connect(self.sendTextMessage)
        self.QPushButton_clear.clicked.connect(self.clearMessage)
        self.QPushButton_connect.clicked.connect(self.connectToServer)
        self.QPushButton_disconnect.clicked.connect(self.disconnectFromServer)

        self.QAction_file.triggered.connect(self.selectFile)
        self.QAction_picture.triggered.connect(self.selectPicture)

        self.timer.timeout.connect(self.resetTimer)

    def toggleEditUserName(self, event):
        # 点击用户名标签时切换为编辑模式
        self.QLabel_userName.setHidden(True)
        self.QLineEdit_userName.setHidden(False)
        self.QLineEdit_userName.setText(self.QLabel_userName.text())
        self.QLineEdit_userName.setFocus()

    @pyqtSlot()
    def updateUserName(self)->None:
        oldName = self.userName
        self.userName = self.QLineEdit_userName.text()
        self.QLabel_userName.setText(self.userName)
        self.QLabel_userName.setHidden(False)
        self.QLineEdit_userName.setHidden(True)
        self.selfListItem.setText(self.userName + ' ' +self.clientIP)
        self.userNameUpdateSignal.emit([self.clientIP, oldName, self.userName])

    @pyqtSlot()
    def updateServerIP(self)->None:
        self.ip = self.QLineEdit_serverIP.text()

    @pyqtSlot()
    def updateServerPort(self)->None:
        self.port = self.QLineEdit_serverPort.text()


    @pyqtSlot()
    def connectToServer(self):
        self.tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            self.tcpSocket.connect((IP, PORT))
        except:
            print('Failed to connect to server')
            return

        self.sendThread = QThread(self)
        self.receiveThread = QThread(self)
        self.sendWorker = SendWorker(self.tcpSocket, IP, PORT)
        self.receiveWorker = ReceiveWorker(self.tcpSocket, IP, PORT)
        self.receiveWorker.moveToThread(self.receiveThread)
        self.sendWorker.moveToThread(self.sendThread)

        self.sendTextWorkSignal.connect(self.sendWorker.sendTextMessage)
        self.sendImageWorkSignal.connect(self.sendWorker.sendImageMessage)
        self.sendFileWorkSignal.connect(self.sendWorker.sendFileMessage)
        self.userNameUpdateSignal.connect(self.sendWorker.sendUserName)

        self.sendWorker.textMessageSent.connect(self.addSentTextToTextEdit)
        self.sendWorker.imageMessageSent.connect(self.addSentImageToTextEdit)
        # self.sendWorker.newUserNameSent.connect(...)
        self.receiveWorker.textMessageReceived.connect(self.addReceivedTextToTextEdit)
        self.receiveWorker.imageMessageReceived.connect(self.addReceivedImageToTextEdit)
        self.receiveWorker.fileMessageReceived.connect(self.saveFileReceived)
        self.receiveWorker.userNamesReceived.connect(self.updateMemeberList)


        self.timer.timeout.connect(self.receiveWorker.listenOnce)

        self.receiveThread.start()
        self.sendThread.start()
        print('Worker thread initialized successfully')
        self.userNameUpdateSignal.emit([self.clientIP, self.userName, self.userName])

        self.switchState(True)

    @pyqtSlot()
    def disconnectFromServer(self):
        if hasattr(self, 'tcpSocket'):
            self.tcpSocket.close()
        self.receiveThread.quit()
        self.sendThread.quit()
        self.receiveThread.wait()
        self.sendThread.wait()
        self.sendWorker = None
        self.receiveWorker = None
        self.receiveThread = None
        self.sendThread = None
        self.tcpSocket = None
        
        self.switchState(False)
    
    @pyqtSlot()
    def sendTextMessage(self):
        text = self.QTextEdit_inputBox.toPlainText()
        if text == '':
            return
        timeHead = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        nameHead = self.userName
        message = [nameHead, timeHead, text]
        self.sendTextWorkSignal.emit(message)
        
    
    @pyqtSlot(dict)
    def updateMemeberList(self, jsonData):
        members = jsonData['members'] # = ['chatter1' , '123.12.3.4', 'chatter2', '1.1.1.11']
        self.memberList = []
        i = 0
        while i < len(members):
            text = members[i] + ' ' + members[i+1]
            self.memberList.append(QListWidgetItem(text))
            i += 2
        self.QListWidget_memberList.clear()
        for member in self.memberList:
            self.QListWidget_memberList.addItem(member)

    def selectFile(self)->str:
        filePath, fileType = QFileDialog.getOpenFileName(self, 'Select picture', './', 'All Files(*)')
        if filePath == '':
            print('No picture selected')
            return
        fileType = os.path.splitext(filePath)[1]
        timeHead = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        nameHead = self.userName
        message = [nameHead, timeHead, filePath, fileType]
        print(message[3])
        self.sendFileWorkSignal.emit(message)
    
    @pyqtSlot()
    def selectPicture(self)->str:
        # 选择图片路径
        imagePath, imageType = QFileDialog.getOpenFileName(self, 'Select picture', './', 'Image files(*.jpg *.png *.jpeg)')
        if imagePath == '':
            print('No picture selected')
            return
        timeHead = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        nameHead = self.userName
        message = [nameHead, timeHead, imagePath]
        self.sendImageWorkSignal.emit(message)

    @pyqtSlot(str)
    def addSentImageToTextEdit(self, imagePath:str):
        cursor = self.QTextEdit_chatBox.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText('\n')
        cursor.movePosition(QTextCursor.End)
        imageFormat = QTextImageFormat()
        imageFormat.setWidth(100)
        imageFormat.setHeight(100)
        imageFormat.setName(imagePath)
        cursor.insertImage(imageFormat)
        cursor.movePosition(QTextCursor.End)
        cursor.insertText('\n')
        cursor.movePosition(QTextCursor.End)

    @pyqtSlot(bytes)
    def addReceivedImageToTextEdit(self, data:bytes):
        # 将接收到的bytes 转换成图片保存在本地, 然后显示在聊天框中
        qimage = QImage.fromData(data)
        nowTime = datetime.now().strftime("%Y%m%d%H%M%S")
        # qimage.save(f'.\\files\\\{nowTime}.png')
        qimage.save(self.getFilePath(f'{nowTime}.png'))

        cursor = self.QTextEdit_chatBox.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText('\n')
        cursor.movePosition(QTextCursor.End)
        imageFormat = QTextImageFormat()
        imageFormat.setWidth(100)
        imageFormat.setHeight(100)
        imageFormat.setName(self.getFilePath(f'{nowTime}.png'))
        cursor.insertImage(imageFormat)
        cursor.movePosition(QTextCursor.End)
        cursor.insertText('\n')
        cursor.movePosition(QTextCursor.End)


    @pyqtSlot(list)
    def saveFileReceived(self, dataList:list):
        # pass
        fileType = dataList[0]
        fileData = dataList[1]
        nowTime = datetime.now().strftime("%Y%m%d%H%M%S")
        # file_path = f'{self.currentDirectory}\\files\\{nowTime}{fileType}'  # 替换为你想要保存的文件路径
        try:
            fileName = f'{nowTime}{fileType}'
            # file_path = f'.\\files\\{nowTime}{fileType}'
            # with open(file_path, 'xb') as file:
            with open(fileName, 'wb') as file:
                file.write(fileData)
        except Exception as e:
            print("failed to save file received")
            print(e)
        else:
            self.addSentTextToTextEdit(FILE_RECEIVED)


    @pyqtSlot()
    def clearMessage(self):
        self.QTextEdit_chatBox.clear()
        self.QTextEdit_inputBox.clear()


    @pyqtSlot(int)
    def addSentTextToTextEdit(self, info:int):
        head = ''
        if info == TEXT_INVALID:
            text = '<font color="red">Message too long, failed to send!</font>'
        elif info == SOCKET_ERR:
            text = '<font color="red">Socket error, failed to send!</font>'
        elif info == FILE_RECEIVED:
            text = '<font color="red">You have received a new file!</font>'
        elif info == TEXT_SENT:
            nowTime = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
            text = self.QTextEdit_inputBox.toPlainText()
            head = '<font color="blue">'+ 'YOU: ' + nowTime +'</font>'
            self.QTextEdit_inputBox.clear()
        self.QTextEdit_chatBox.textCursor().movePosition(QTextCursor.End)
        self.QTextEdit_chatBox.insertHtml(head)
        self.QTextEdit_chatBox.append('') # \n
        self.QTextEdit_chatBox.insertHtml(text)
        self.QTextEdit_chatBox.textCursor().movePosition(QTextCursor.End)
        self.QTextEdit_chatBox.append('')
        # insert '\n' in insertHtml() does not work (maybe i should use <\br>)

    @pyqtSlot(dict)
    def addReceivedTextToTextEdit(self, jsonData:dict):
        userName = jsonData['user']
        sendTime = jsonData['time']
        text = jsonData['textContent']
        self.QTextEdit_chatBox.textCursor().movePosition(QTextCursor.End)
        text = userName + ': ' + sendTime + '\n' + text
        self.QTextEdit_chatBox.append(text)
        self.QTextEdit_chatBox.append('')
        self.QTextEdit_chatBox.textCursor().movePosition(QTextCursor.End)

    
    # @override
    def closeEvent(self, event):
        if hasattr(self, 'tcpSocket'):
            self.disconnectFromServer()
        event.accept()


# Socket client running on Windows
if __name__ == '__main__':
    app = QApplication(sys.argv)
    font = app.font()
    font.setFamily('Microsoft YaHei')
    app.setFont(font)

    mainWindow = ClientGUI()

    mainWindow.show()
    app.exec_()
